
import os
from pprint import pprint

from .curves import Point
from .bitcoin import BITCOIN
from .sha256 import sha256
from .ripemd160 import ripemd160


def gen_secret_key(n: int) -> int:
    """
      n是钥匙上的上限，通常是椭圆曲线的顺序
     我们正在使用。该函数将返回有效的密钥，即1 <=键<n
    """
    while True:
        key = int.from_bytes(os.urandom(32))
        pprint(key)
        if 1 <= key < n:
            break # the key is valid, break out
    return key

# -----------------------------------------------------------------------------
# Public key - specific functions, esp encoding / decoding

class PublicKey(Point):
    """
    The public key is just a Point on a Curve, but has some additional specific
    encoding / decoding functionality that this class implements.
    """

    @classmethod
    def from_point(cls, pt: Point):
        pprint(cls (pt.curve, pt.x, pt.y ))
        return cls(pt.curve, pt.x, pt.y)
    @staticmethod
    def  fuck():
        print("fuck")

    @classmethod
    def from_sk(cls, sk):
        """ sk can be an int or a hex string """
        pprint(cls)
        assert isinstance(sk, (int, str))
        sk = int(sk, 16) if isinstance(sk, str) else sk
        pk = sk * BITCOIN.gen.G
        pprint(pk)
        return cls.from_point(pk)

    @classmethod
    def decode(cls, b: bytes):
        """ decode from the SEC binary format """
        assert isinstance(b, bytes)

        # the uncompressed version is straight forward
        if b[0] == 4:
            x = int.from_bytes(b[1:33])
            y = int.from_bytes(b[33:65])
            return Point(BITCOIN.gen.G.curve, x, y)

        # for compressed version uncompress the full public key Point
        # first recover the y-evenness and the full x
        assert b[0] in [2, 3]
        is_even = b[0] == 2
        x = int.from_bytes(b[1:])

        # solve y^2 = x^3 + 7 for y, but mod p
        p = BITCOIN.gen.G.curve.p
        y2 = (pow(x, 3, p) + 7) % p
        y = pow(y2, (p + 1) // 4, p)
        y = y if ((y % 2 == 0) == is_even) else p - y # flip if needed to make the evenness agree
        return cls(BITCOIN.gen.G.curve, x, y)

    def encode(self, compressed, hash160=False):
        """ return the SEC bytes encoding of the public key Point """
        # calculate the bytes
        if compressed:
            prefix = b'\x02' if self.y % 2 == 0 else b'\x03'
            pkb = prefix + self.x.to_bytes(32)
            pprint(pkb)
        else:
            pkb = b'\x04' + self.x.to_bytes(32) + self.y.to_bytes(32)
        # hash if desired
        return ripemd160(sha256(pkb)) if hash160 else pkb

    def address(self, net: str, compressed: bool) -> str:
        """ return the associated bitcoin address for this public key as string """
        # encode the public key into bytes and hash to get the payload
        pkb_hash = self.encode(compressed=compressed, hash160=True)
        # add version byte (0x00 for Main Network, or 0x6f for Test Network)
        version = {'main': b'\x00', 'test': b'\x6f'}
        ver_pkb_hash = version[net] + pkb_hash
        pprint(ver_pkb_hash)
        # calculate the checksum
        '''
        校验和的作用是通过双重哈希来确保数据的完整性，即使是非常微小的数据变化也会导致哈希值的变化，
        从而使得校验和不匹配，这样可以快速检测到地址的错误。
        '''
        checksum = sha256(sha256(ver_pkb_hash))[:4] # 前四个字节
        pprint(checksum)
        # append to form the full 25-byte binary Bitcoin Address
        byte_address = ver_pkb_hash + checksum
        pprint(byte_address)
        # finally b58 encode the result
        """
        Base58 编码是一种变种的 Base64 编码，它去掉了容易引起歧义的字符（如大写字母 O、小写字母 l 和数字 0），
        并添加了一些额外的字符（如 I、B、U 和数字 1），以减少编码后的字符串长度并提高可读性。
        """
        b58check_address = b58encode(byte_address)
        return b58check_address

# -----------------------------------------------------------------------------
# convenience functions

def gen_key_pair():
    """ generate a (secret, public) key pair in one shot """
    sk = gen_secret_key(BITCOIN.gen.n)
    pk = PublicKey.from_sk(sk)
    return sk, pk


alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
alphabet_inv = {c:i for i,c in enumerate(alphabet)}

def b58encode(b: bytes) -> str:
    assert len(b) == 25 # version is 1 byte, pkb_hash 20 bytes, checksum 4 bytes
    n = int.from_bytes(b)
    pprint(n)
    chars = []
    while n:
        n, i = divmod(n, 58) #  n//58, n%58
        chars.append(alphabet[i])
    # special case handle the leading 0 bytes... ¯\_(ツ)_/¯
    num_leading_zeros = len(b) - len(b.lstrip(b'\x00'))
    pprint(num_leading_zeros)
    pprint(''.join(chars))
    res = num_leading_zeros * alphabet[0] + ''.join(reversed(chars))
    return res

def b58decode(res: str) -> bytes:
    n = sum(alphabet_inv[c] * 58**i for i, c in enumerate(reversed(res)))
    return n.to_bytes(25)  # version, pkb_hash, checksum bytes

def address_to_pkb_hash(b58check_address: str) -> bytes:
    """ given an address in b58check recover the public key hash """
    byte_address = b58decode(b58check_address)
    # validate the checksum
    assert byte_address[-4:] == sha256(sha256(byte_address[:-4]))[:4]
    # strip the version in front and the checksum at tail
    pkb_hash = byte_address[1:-4]
    return pkb_hash


